package backend;
/**
 * database room-draw implementation
 *
 * @author Matej D. Voboril
 * @version 1.0 Edited: 4/2013
 */
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.*;
import java.util.*;
import java.util.Date;
import java.util.logging.*;
import javax.swing.*;
//import com.mysql.jdbc.Driver;


public class RoomMatch {
    private static final DateFormat D_F = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
    //private static final Date RUN_ON=new Date(2013,5,10);
    private static final Date RUN_ON = new Date(2013, 4, 1);
    private static final boolean DEF = false;
    private static boolean seniors = DEF;
    private static boolean juniors = DEF;
    private static boolean sophomores = DEF;
    private static final long SLEEP_TIME = 1000 * 60 * 0;
    private static final ArrayList<Room> ROOMS = new ArrayList<Room>();
    private static final ArrayList<Circle> CIRCLES = new ArrayList<Circle>();
    private static final ArrayList<Person> PEOPLE = new ArrayList<Person>();
    private static final ArrayList<Pair> PAIRS = new ArrayList<Pair>();
    private static final ArrayList<Choice> CHOICES = new ArrayList<Choice>();
    //new Date().after(RUN_ON)

    /**
     * Runs the matching algorithm for the three groups.
     */
    public static void main(String[] args) {
        if (true) {
            pullRooms();
            pullPeople();
            findPairs();
            pullCircle();
            sortCircles();
            pullChoices();
            seniors = sortSeniors();
            if (seniors) { //only run if seniors have been matched
                juniors = sortJuniors();
                if (juniors) { //only run if juniors have been matched
                    sophomores = sortSophomores();
                }
            }
            try {
                commitRooms();
            } catch (SQLException ex) {
                System.err.println("SQL exception: " + ex.getMessage());
            }
        }
    }
    //============================Match Making=========================\\
    /**
     * The implementation of the matchmaking algorithm
     *
     * @param a upper limit of matchmaking random numbers
     * @param b lower limit of matchmaking random numbers
     * @return matchMade whether or not the chosen group has been matched
     */
    static boolean matchMake(int priority) {
        //run the match
        ArrayList<Circle> currSet = new ArrayList<Circle>();
        boolean matchMade = false;

        //Put all the circles with the current priority into a list
        for (int i = 0; i < CIRCLES.size(); i++) {
            if (CIRCLES.get(i).priority == priority) {
                currSet.add(CIRCLES.get(i));
            }
        }
        //Sort circles by rnd num of the circle leader
        Collections.sort(currSet);
        //fill the choices ArrayList
        for (int i = 0; i < currSet.size(); i++) {
            Circle currCircle = currSet.get(i);
            //Create a list of rooms chosen by the current circle
            ArrayList<Room> cCP = new ArrayList<Room>();
            for (int j = 0; j < CHOICES.size(); j++) {
                if (CHOICES.get(j).person == currCircle.leader) {
                    cCP.add(CHOICES.get(j).room);
                }
            }
            int k = 0;
            int needed = currCircle.members.size();
            //assign each circle their room(s) while the members exceed the capacity of those rooms taken.
            while (k < needed) {
                //If first choice not available, check subsequent choices
                //if the whole circle doesn't fit in the room, get the next room in their priority 
                //that they would fill and make it theirs as well
                if (!cCP.get(k).taken && cCP.get(k).capacity <= needed && cCP.get(k).gender.equals(currCircle.leader.gender)) {
                    needed -= cCP.get(k).capacity;
                    //give persons that don't have a room one of the rooms
                    for (int j = 0; j < cCP.get(k).capacity; j++) {
                        ROOMS.get(ROOMS.indexOf(cCP.get(k))).members.add(currCircle.members.get(j));
                    }
                    //set room.taken=true once a room is assigned to a circle
                    cCP.get(k).taken = true;
                    k = 0;
                } else if (cCP.get(k).taken || cCP.get(k).capacity > needed) {
                    k++;//Skip if the room's too big or taken
                } else {
                    //if all of their choices are exhausted, get the first available room.
                    for (int j = 0; j < ROOMS.size(); j++) {
                        if (!ROOMS.get(j).taken && ROOMS.get(j).capacity <= needed && cCP.get(k).gender.equals(currCircle.leader.gender)) {
                            for (int m = 0; m < cCP.get(k).capacity; m++) {
                                ROOMS.get(j).members.add(currCircle.members.get(m));
                                //set room.taken=true once a room is assigned to a circle
                                ROOMS.get(j).taken = true;
                            }
                        }
                    }
                    k++;
                }
                matchMade = true;
            }
        }
        return matchMade;
    }

        /**
     *
     * @return sen For denoting that the seniors have been sorted
     */
    static boolean sortSeniors() {
        //Call the match for random numbers of <2000000 && > 1000000
        boolean sen1 = matchMake(1);
        boolean sen2 = matchMake(2);
        boolean sen3 = matchMake(3);
        boolean sen4 = matchMake(4);
        sleep();
        return sen1 && sen2 && sen3 && sen4;
    }

    /**
     *
     * @return jun For denoting that the juniors have been matched or not
     */
    static boolean sortJuniors() {
        //Call the match for random numbers of <3000000 && > 2000000
        boolean jun1 = matchMake(5);
        boolean jun2 = matchMake(6);
        sleep();
        return jun1 && jun2;
    }

    /**
     *
     * @return sop For denoting that the sophomores have been matched or not
     */
    static boolean sortSophomores() {
        //Call the match for random numbers of <4000000 && > 3000000
        boolean sop = matchMake(7);
        sleep();
        return sop;
    }
    
    
    //===============================Misc==================================\\
    /**
     * Sleeps the thread for 5 minutes (or whatever SLEEP_TIME is set,
     * allows server time to rest between sorts
     */
    static void sleep() {
        try {
            //Thread.sleep(SLEEP_TIME);
            Thread.sleep(0);
        } catch (InterruptedException ex) {
            Logger.getLogger(RoomMatch.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    
    //====================Methods for modifying ArrayLists====================\\
    /**
     * sort circles internally by
     */
    static void sortCircles() {
        for (int i = 0; i < CIRCLES.size(); i++) {
            CIRCLES.get(i).orgCircle();
        }
        Collections.sort(CIRCLES);
    }
    
    /**
     * create all the circles
     */
    static void pullCircle() {
        for (int i = 0; i < PAIRS.size(); i++) {
            for (int j = 0; j < CIRCLES.size(); j++) {
                final Pair currPair = PAIRS.get(i);
                final Circle currCircle = CIRCLES.get(j);

                if (currCircle.leader.sid == currPair.friend1.sid) {
                    if (!currCircle.members.contains(currPair.friend2)) {
                        currCircle.members.add(currPair.friend2);
                    }
                } else {
                    Circle curr = new Circle(currPair.friend1);
                    curr.members.add(currPair.friend2);

                }
            }
        }
        for (int i = 0; i < PEOPLE.size(); i++) {
            final Person currPerson = PEOPLE.get(i);
            boolean isInGroup = false;
            for (int j = 0; j < CIRCLES.size(); j++) {
                final Circle currCircle = CIRCLES.get(j);

                if (currCircle.members.contains(currPerson)) {
                    isInGroup = true;
                }
            }
            if (!isInGroup) {
                CIRCLES.add(new Circle(currPerson));
            }
        }

    }

    
    //=======================Database Access Methods====================\\
    /**
     * gets all the rooms and adds them to the room array list
     */
    static void pullRooms() {
        loadDriver();
        Connection conn = establishConnection();
        Statement s = prepStatement(conn);
        ResultSet r = null;
        try {
            r = s.executeQuery(
                    "SELECT r.RID as room_id, r.capacity as capacity, b.gender"
                    + "FROM rooms as r, buildings as b"
                    + "WHERE b.bid=r.rid;");
            while (r.next()) {
                int rid = r.getInt("room_id");
                int capacity = r.getInt("capacity");
                String gender = r.getString("gender");
                ROOMS.add(new Room(rid, capacity, gender));
            }
        } catch (SQLException e) {
            System.err.println("SQL exception: " + e.getMessage());
            System.err.println("SQL state: " + e.getSQLState());
            System.err.println("Error code: " + e.getErrorCode());
        }
    }

    /**
     * Organizes from PEOPLE into PAIRS
     */
    static void findPairs() {
        loadDriver();
        Connection conn = establishConnection();
        Statement s = prepStatement(conn);
        ResultSet r = null;
        try {
            r = s.executeQuery(
                    "SELECT Friend1, Friend2"
                    + "FROM friends");
            while (r.next()) {
                int f1 = r.getInt("Friend1");
                int f2 = r.getInt("Friend2");
                Person friend1 = new Person();
                Person friend2 = new Person();
                for (int i = 0; i < PEOPLE.size(); i++) {
                    if (f1 == PEOPLE.get(i).sid) {
                        friend1 = PEOPLE.get(i);
                    }
                    if (f2 == PEOPLE.get(i).sid) {
                        friend2 = PEOPLE.get(i);
                    }
                }
                PAIRS.add(new Pair(friend1, friend2));
            }
        } catch (SQLException e) {
            System.err.println("SQL exception: " + e.getMessage());
            System.err.println("SQL state: " + e.getSQLState());
            System.err.println("Error code: " + e.getErrorCode());
        }
    }

    /**
     * pulls all people
     */
    static void pullPeople() {
        loadDriver();
        Connection conn = establishConnection();
        Statement s = prepStatement(conn);
        ResultSet r = null;
        try {
            r = s.executeQuery(
                    "SELECT SID, gender, Social_Status, Rnd_Num"
                    + "FROM users");
            while (r.next()) {
                int sid = r.getInt("SID");
                String gender = r.getString("gender");
                int ss = r.getInt("Social_Status");
                int rnd = r.getInt("Rnd_Num");
                PEOPLE.add(new Person(sid, gender, rnd, ss));
            }
        } catch (SQLException e) {
            System.err.println("SQL exception: " + e.getMessage());
            System.err.println("SQL state: " + e.getSQLState());
            System.err.println("Error code: " + e.getErrorCode());
        }
    }

    

    /**
     * get the circles' choices and
     */
    static void pullChoices() {
        loadDriver();
        Connection conn = establishConnection();
        Statement s = prepStatement(conn);
        ResultSet r = null;
        try {
            r = s.executeQuery(
                    "SELECT sid, rid"
                    + "FROM choices");
            while (r.next()) {
                int sid = r.getInt("sid");
                int rid = r.getInt("rid");
                Room room = new Room();
                Person person = new Person();
                for (int i = 0; i < PEOPLE.size(); i++) {
                    Person currPerson = PEOPLE.get(i);
                    if (sid == currPerson.sid) {
                        person = currPerson;
                    }
                }
                for (int i = 0; i < ROOMS.size(); i++) {
                    Room currRoom = ROOMS.get(i);
                    if (sid == currRoom.rid) {
                        room = currRoom;
                    }
                }
                CHOICES.add(new Choice(person, room));
            }
            conn.close();
        } catch (SQLException e) {
            System.err.println("SQL exception: " + e.getMessage());
            System.err.println("SQL state: " + e.getSQLState());
            System.err.println("Error code: " + e.getErrorCode());
        }
        
    }

    private static void commitRooms() throws SQLException {
        loadDriver();
        Connection conn = establishConnection();
        PreparedStatement updateRooms = null;
        for (int i = 0; i < ROOMS.size(); i++) {
            Room currRoom = ROOMS.get(i);
            for (int j = 0; j < currRoom.members.size(); j++) {
                Person currPerson = currRoom.members.get(j);
                try {
                    String addRoom = "UPDATE users"
                            + "SET room=" + currRoom.rid
                            + "WHERE sid=" + currPerson.sid;
                    conn.setAutoCommit(false);
                    updateRooms = conn.prepareStatement(addRoom);
                    updateRooms.executeUpdate();
                    conn.commit();
                } catch (SQLException e) {
                    System.err.println("SQL exception: " + e.getMessage());
                    System.err.println("SQL state: " + e.getSQLState());
                    System.err.println("Error code: " + e.getErrorCode());
                    try {
                        conn.rollback();
                    } catch (SQLException ex) {
                        Logger.getLogger(RoomMatch.class.getName()).log(Level.SEVERE, null, ex);
                    }
                } finally {
                    if (updateRooms == null) {
                        updateRooms.close();
                    }
                    conn.setAutoCommit(true);
                }
            }
        }
        conn.close();
    }
    
    //======================Utility methods==========================\\
    /**
     * Load jdbc.Driver
     */
    private static void loadDriver() {
        //Load driver
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance();
        } catch (ClassNotFoundException e) {
            System.err.println("Error when loading DB driver:");
            System.err.println(e);
        }
        catch(InstantiationException e){
            System.err.println("Error when loading DB driver:");
            System.err.println(e);
        }
        catch(IllegalAccessException e){
            System.err.println("Error when loading DB driver:");
            System.err.println(e);
        }
    }

    private static Connection establishConnection() {
        //Establish connection
        Connection conn = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://bccs.ravens.bc/ROOMDRAW",
                    "vollun", "spreadability");
            System.out.println("Connection to database established.");
        } catch (SQLException e) {
            System.err.println("SQL exception: " + e.getMessage());
            System.err.println("SQL state: " + e.getSQLState());
            System.err.println("Error code: " + e.getErrorCode());
        }
        return conn;
    }

    private static Statement prepStatement(Connection conn) {
        Statement s = null;
        try {
            if(conn != null)
                s = conn.createStatement();
        } catch (SQLException e) {
            System.err.println("SQL exception: " + e.getMessage());
            System.err.println("SQL state: " + e.getSQLState());
            System.err.println("Error code: " + e.getErrorCode());
        }
        return s;
    }
}

class Room {

    int rid;
    int capacity;
    String gender;
    boolean taken = false;
    ArrayList<Person> members = new ArrayList<Person>();

    public Room(int rid, int capacity, String gender) {
        this.capacity = capacity;
        this.rid = rid;
        this.gender = gender;
    }

    public Room() {
    }

    public void add(Person a) {
        if (members.size() < capacity) {
            members.add(a);
        }
    }

    public void remove(Person a) {
        if (members.contains(a)) {
            int x = members.lastIndexOf(a);
            members.remove(x);
        }
    }
}

class Circle implements Comparable<Circle> {

    public ArrayList<Person> members = new ArrayList<Person>();
    public Person leader;
    public Person commander;
    public Person lieutenant;
    public static final int SENIOR_SENIOR = 1;
    public static final int SENIOR_JUNIOR = 2;
    public static final int SENIOR_JUNIOR_SOPHOMORE = 3;
    public static final int SENIOR_SOPHOMORE = 4;
    public static final int JUNIOR_JUNIOR = 5;
    public static final int JUNIOR_SOPHOMORE = 6;
    public static final int SOPHOMORE_SOPHOMORE = 7;
    public boolean has_sen = false;
    public boolean has_jun = false;
    public boolean has_sop = false;
    public int priority;

    public Circle(Person leader) {
        this.leader = leader;
        members.add(leader);
        priority = leader.ss;
    }

    @Override
    public int compareTo(Circle o) {
        int comp = 0;
        if (this.leader.rnd < o.leader.rnd) {
            comp = -1;
        } else if (this.leader.rnd == o.leader.rnd) {
            comp = 0;
        } else if (this.leader.rnd > o.leader.rnd) {
            comp = 1;
        }
        return comp;
    }

    public void orgCircle() {
        //reassign leader to member with smaller rnd number
        for (int i = 0; i < this.members.size(); i++) {
            if (this.members.get(i).compareTo(leader) < 0) {
                this.leader = this.members.get(i);
            }
        }

        //sets commander and lieutenant (not used?)
        if (this.members.size() > 2) {
            Collections.sort(members);
            this.commander = this.members.get(1);
            this.lieutenant = this.members.get(this.members.size() - 1);
        } else if (this.members.size() == 2) {
            this.commander = this.members.get(1);
            this.lieutenant = this.members.get(1);
        } else if (this.members.size() == 1) {
            this.commander = this.members.get(0);
            this.lieutenant = this.members.get(0);
        }

        //Determines social status makeup of the group
        for (int i = 0; i < this.members.size(); i++) {
            Person currMeme = this.members.get(i);
            if (currMeme.ss == 3 || currMeme.ss == 4) {
                this.has_sen = true;
            }
            if (currMeme.ss == 2) {
                this.has_jun = true;
            }
            if (currMeme.ss == 1) {
                this.has_sop = true;
            }
        }

        //Determines priority of the group based on social status makeup
        if (this.has_sen && !this.has_jun && !this.has_sop) {
            this.priority = SENIOR_SENIOR;
        } else if (this.has_sen && this.has_jun && !this.has_sop) {
            this.priority = SENIOR_JUNIOR;
        } else if (this.has_sen && this.has_jun && this.has_sop) {
            this.priority = SENIOR_JUNIOR_SOPHOMORE;
        } else if (this.has_sen && !this.has_jun && this.has_sop) {
            this.priority = SENIOR_SOPHOMORE;
        } else if (!this.has_sen && this.has_jun && !this.has_sop) {
            this.priority = JUNIOR_JUNIOR;
        } else if (!this.has_sen && this.has_jun && this.has_sop) {
            this.priority = JUNIOR_SOPHOMORE;
        } else if (!this.has_sen && !this.has_jun && this.has_sop) {
            this.priority = SOPHOMORE_SOPHOMORE;
        }
    }
}

class Person implements Comparable<Person> {

    public int sid;
    public String gender;
    public int rnd;
    public int ss;

    public Person(int sid, String gender, int rnd, int ss) {
        this.sid = sid;
        this.gender = gender;
        this.rnd = rnd;
        this.ss = ss;
    }

    public Person() {
    }

    @Override
    public int compareTo(Person o) {
        int comp = 0;
        if (this.rnd < o.rnd) {
            comp = -1;
        } else if (this.rnd == o.rnd) {
            comp = 0;
        } else if (this.rnd > o.rnd) {
            comp = 1;
        }
        return comp;
    }
}

class Pair {

    public Person friend1;
    public Person friend2;

    public Pair(Person friend1, Person friend2) {
        this.friend1 = friend1;
        this.friend2 = friend2;
    }
}

class Choice {

    public Person person;
    public Room room;

    public Choice(Person person, Room room) {
        this.person = person;
        this.room = room;
    }
}
